#include "levelparser.h"
#include "atlasparser.h"
#include "../enum/fontselector.h"

LevelParser::LevelParser(std::string path)
	: file {path}
{}

LevelParser::~LevelParser()
{
	if (file.is_open())
		file.close();
}

bool LevelParser::dry_run()
{
	bool has_layer = false;
	bool has_atlas = false;
	bool has_source = false;
	bool has_scalable = false;
	LevelToken token { LevelToken::INVALID };
	LevelToken last { LevelToken::INVALID };

	for (std::string line; getline(file, line);) {
		if (token != LevelToken::EMPTY && token != LevelToken::COMMENT)
			last = token;

		token = match_token(line);

		if (token == LevelToken::EMPTY || token == LevelToken::COMMENT)
			continue;

		if (token == LevelToken::INVALID) {
			SDL_Log("parsing error: invalid token encountered: %s\n", line.c_str());
			return false;
		}

		if ((token == LevelToken::SCALE || token == LevelToken::SCALE_MULT) && !has_scalable) {
			SDL_Log("parsing error: scale directive has to follow a scalable element: %s\n", line.c_str());
			return false;
		}

		if (last == LevelToken::DESCRIPTOR_DRAWABLE && token != LevelToken::DRAWABLE) {
			SDL_Log("parsing error: drawable block <int world_x, int world_y, int id> has to follow a drawable descriptor: %s\n", line.c_str());
			return false;
		}

		if (last == LevelToken::DESCRIPTOR_PLAYER && token != LevelToken::PLAYER) {
			SDL_Log("parsing error: player block <int world_x, int world_y, int id1, int id2, int id3, int id4> has to follow a player descriptor: %s\n", line.c_str());
			return false;
		}

		if (last == LevelToken::DESCRIPTOR_IMAGE && token != LevelToken::IMAGE) {
			SDL_Log("parsing error: image block <int screen_x, int screen_y, \"path\", string path> has to follow an image descriptor: %s\n", line.c_str());
			return false;
		}

		if (last == LevelToken::DESCRIPTOR_TEXT && token != LevelToken::TEXT) {
			SDL_Log("parsing error: text block <int screen_x, int screen_y, (\"small\"|\"medium\"|\"big\"), string text> has to follow a text descriptor: %s\n", line.c_str());
			return false;
		}

		if (token == LevelToken::DRAWABLE && !has_layer && !has_atlas) {
			SDL_Log("parsing error: drawable descriptor only allowed after at least one layer, atlas and source: %s\n", line.c_str());
			return false;
		}

		if (token == LevelToken::PLAYER && !has_layer && !has_atlas) {
			SDL_Log("parsing error: player descriptor only allowed after at least one layer, atlas and source: %s\n", line.c_str());
			return false;
		}

		if ((token == LevelToken::IMAGE || token == LevelToken::TEXT) && !has_layer) {
			SDL_Log("parsing error: image and text descriptors only allowed after at least one layer: %s\n", line.c_str());
			return false;
		}

		has_layer = has_layer || token == LevelToken::LAYER;
		has_atlas = has_atlas || token == LevelToken::ATLAS;
		has_source = has_source || token == LevelToken::SOURCE;
		has_scalable = has_scalable || (token == LevelToken::DRAWABLE || token == LevelToken::IMAGE || token == LevelToken::TEXT);
	}

	file.clear();
	file.seekg(0);

	return has_layer && has_atlas && has_source;
}

std::vector<Layer> LevelParser::parse(Graphics& graphics)
{
	std::vector<Layer> layers;
	std::vector<Sprite> current_atlas;
	SDL_Texture *current_source;
	/* user is expected to use layer token for layer 0 */
	int layer = -1, offset_x = 0, offset_y = 0;
	bool moves = false;
	Drawable *last_drawable = nullptr;

	LevelToken token { LevelToken::INVALID };
	LevelToken last { LevelToken::INVALID };

	for (std::string line; getline(file, line);) {
		if (token != LevelToken::EMPTY && token != LevelToken::COMMENT)
			last = token;

		token = match_token(line);

		if (token == LevelToken::EMPTY || token == LevelToken::COMMENT)
			continue;

		if (token == LevelToken::LAYER) {
			++layer;
			layers.push_back(Layer(layer));
			continue;
		}

		std::istringstream line_stream(line);

		if (token == LevelToken::OFFSET) {
			line_stream >> offset_x >> offset_y;
			continue;
		}

		if (token == LevelToken::SCALE && last_drawable != nullptr) {
			int scale_x, scale_y;
			/* 6 = len("scale ") */
			line_stream.ignore(6);
			line_stream >> scale_x >> scale_y;
			SDL_Log("scale absolute %i %i\n", scale_x, scale_y);
			last_drawable->scale(POS, scale_x, scale_y)
			.scale(DST, POS);
			continue;
		}

		if (token == LevelToken::SCALE_MULT && last_drawable != nullptr) {
			float scale;
			/* 6 = len("scale ") */
			line_stream.ignore(6);
			line_stream >> scale;
			SDL_Log("scale mult %f\n", scale);
			last_drawable->scale(POS, scale)
			.scale(DST, POS);
			continue;
		}

		if (token == LevelToken::MOVES) {
			/* 6 = len("moves ") */
			line_stream.ignore(6);
			line_stream >> std::boolalpha >> moves;
			continue;
		}

		if (token == LevelToken::ATLAS) {
			std::string current_atlas_path;
			/* 6 = len("atlas ") */
			line_stream.ignore(6);
			line_stream >> current_atlas_path;
			AtlasParser ap { current_atlas_path };
			if (!ap.dry_run()) {
				SDL_Log("error parsing %s\n", current_atlas_path.c_str());
				exit(EXIT_FAILURE);
			}
			current_atlas = ap.parse();
			continue;
		}

		if (token == LevelToken::SOURCE) {
			std::string current_source_path;
			/* 7 = len("source ") */
			line_stream.ignore(7);
			line_stream >> current_source_path >> current_source_path;
			current_source = graphics.load_png_to_texture(current_source_path);
			continue;
		}

		if (token == LevelToken::DRAWABLE) {
			int world_x, world_y;
			int id;
			line_stream >> world_x >> world_y >> id;

			layers[layer].register_atlas(current_source);
			Drawable d { nullptr, moves, current_atlas[id] };
			d.move(POS, world_x + offset_x, world_y + offset_y)
			.move(DST, POS)
			.scale(POS, current_atlas[id].src.w, current_atlas[id].src.h)
			.scale(DST, POS);
			layers[layer].drawables.push_back(d);
			last_drawable = &layers[layer].drawables.back();

			continue;
		}

		if (token == LevelToken::PLAYER) {
			int world_x, world_y;
			int id[4];
			line_stream >> world_x >> world_y >> id[0] >> id[1] >> id[2] >> id[3];

			layers[layer].register_player(current_source, moves);
			layers[layer].player.Drawable::move(DST, world_x + offset_x, world_y + offset_y)
			.move(DST, POS)
			.scale(POS, current_atlas[id[0]].src.w, current_atlas[id[0]].src.h)
			.scale(DST, POS);
			for (int i = 0; i < 4; i++)
				layers[layer].player.register_sprite(current_atlas[id[i]]);

			continue;
		}

		if (token == LevelToken::IMAGE) {
			int screen_x, screen_y;
			std::string path;
			line_stream >> screen_x >> screen_y;
			/* 6 = len(" path ") */
			line_stream.ignore(6);
			std::getline(line_stream, path);

			SDL_Texture *texture = graphics.load_png_to_texture(path);

			Sprite s { texture };
			Drawable d { texture, moves, s };
			d.move(POS, screen_x, screen_y)
			.move(DST, POS)
			.scale(POS, s.src.w, s.src.h)
			.scale(DST, POS);
			layers[layer].drawables.push_back(d);
			last_drawable = &layers[layer].drawables.back();

			continue;
		}

		if (token == LevelToken::TEXT) {
			int screen_x, screen_y;
			unsigned int color;
			std::string fontsize, text;
			line_stream >> screen_x >> screen_y >> std::hex >> color >> fontsize;
			/* space character before text */
			line_stream.ignore(1);
			std::getline(line_stream, text);

			SDL_Texture *texture = graphics.load_text_to_texture(
				text,
				{
					.r = static_cast<uint8_t>((color & 0xff000000) >> 24),
					.g = static_cast<uint8_t>((color & 0x00ff0000) >> 16),
					.b = static_cast<uint8_t>((color & 0x0000ff00) >> 8),
					.a = static_cast<uint8_t>((color & 0x000000ff) >> 0),
				},
				(fontsize == "small") ? FONT_SMALL : ((fontsize == "big") ? FONT_BIG : FONT_MEDIUM)
			);

			Sprite s { texture };
			Drawable d { texture, moves, s };
			d.move(POS, screen_x, screen_y)
			.move(DST, POS)
			.scale(POS, s.src.w, s.src.h)
			.scale(DST, POS);
			layers[layer].drawables.push_back(d);
			last_drawable = &layers[layer].drawables.back();

			continue;
		}
	}

	return layers;
}

LevelToken LevelParser::match_token(std::string line)
{
	for (auto& matcher : matchings)
		if (regex_search(line, mode, matcher.first))
			return matcher.second;

	return LevelToken::INVALID;
}